<!DOCTYPE html>
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>文件管理/上传示例</title>
    <link rel="shortcut icon" href="{$app.favicon|default:"`$domain.main`/favicon.png"}">
    <!-- vue -->
    <script src="{static url='/third-party/vue/3.2.47.prod.min.js'}"></script>
    <!-- axios -->
    <script src="{static url='/third-party/axios/1.3.4.min.js'}"></script>
    <!-- ant design vue 4.0.1 2023.8.26 更新 -->
    <script src="{static url='/third-party/dayjs/dayjs.min.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/customParseFormat.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/weekday.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/localeData.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/weekOfYear.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/weekYear.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/advancedFormat.js'}"></script>
    <script src="{static url='/third-party/dayjs/plugin/quarterOfYear.js'}"></script>
    <script src="{static url='/third-party/antd-vue-v4/antd-with-locales.min.js'}"></script>
    <link href="{static url='/third-party/antd-vue-v4/reset.css'}" rel="stylesheet">
    <!-- font -->
    <link href="{static url='/third-party/fa/css.css'}" rel="stylesheet">
    <!-- 框架公共样式 -->
    <link href="{assets url='/style/global.css'}{if $smarty.const.DEV_MODE}?{$smarty.now}{/if}" rel="stylesheet">
    <!-- 内部样式 -->
    <link href="{assets url='style/style.css' module='fs'}{if $smarty.const.DEV_MODE}?{$smarty.now}{/if}" rel="stylesheet">
    <!-- 本地 md5 计算 -->
    <script src="{static url='/third-party/spark-md5/js.js'}"></script>
</head>
<body style="overflow: hidden;">
<!-- 容器 -->
<div id="app-outlet" class="fs-outlet">
    <!-- 上传控件 -->
    <input ref="fileInput" type="file" :accept="accept || 'null'" :multiple="multiple" style="display: none;" @change="startUp">
    <!-- 图片预览 -->
    <a-image :src="preview" :preview="{ visible:previewVisible, onVisibleChange: setPreviewVisible }" style="display: none;"></a-image>
    <!-- 文件夹创建、修改文件名 -->
    <a-modal
            v-model:visible="edit.show"
            :title="edit.id > 0 ? '修改名称' : '创建文件夹'"
            :confirm-loading="edit.loading"
            cancel-text="取消"
            ok-text="保存"
            @ok="save">
        <a-input type="text" v-model:value="edit.name" placeholder="文件(夹)名称" :maxlength="100" @keyup.enter="save" />
    </a-modal>
    <div class="ui flex-item fs-header padding">
        <div class="start">
            <a-radio-group v-model:value="listType" button-style="solid">
                <a-radio-button value="grid"><i class="fa fa-image"></i></a-radio-button>
                <a-radio-button value="list"><i class="fa fa-list"></i></a-radio-button>
            </a-radio-group>
        </div>
        <div class="start">
            <a-input-search
                    v-model:value="keyword"
                    placeholder="输入文件名称搜索"
                    enter-button="搜索"
                    @search="setKeyword"
            />
        </div>
        <div class="start" v-if="listQueryParams.keyword != ''">
            <a-button type="danger" @click="setKeyword('')">
                <template #icon><i class="fa fa-close margin-right-half"></i></template>
                清除搜索
            </a-button>
        </div>
        <div class="center"></div>
        <div class="end padding-left">
            <a-button type="primary" danger @click="editFile(0, '')">
                <template #icon><i class="fa fa-folder-plus margin-right-half"></i></template>
                添加文件夹
            </a-button>
        </div>
        <div class="end padding-left">
            <a-button type="primary" @click="clickUp()">
                <template #icon><i class="fa fa-up margin-right-half"></i></template>
                上传文件
            </a-button>
        </div>
    </div>
    <!-- 父级选择 -->
    <div class="fs-header ui flex-item padding">
        <div class="start" v-if="!selectable || multi">
            <a-checkbox v-model:checked="checkedAll" @change="checkAllChange">全选</a-checkbox>
        </div>
        <div class="center">
            <a-breadcrumb>
                <a-breadcrumb-item href="javascript:void(0);" @click="setParent(0)">
                    <i class="fa fa-bookmark margin-right-half"></i>
                    <span>根目录</span>
                </a-breadcrumb-item>
                <a-breadcrumb-item href="javascript:void(0);" v-for="p in parents" @click="setParent(p.id)">{{ p.name }}</a-breadcrumb-item>
            </a-breadcrumb>
        </div>
        <div class="end" v-if="checkedRows > 0">
            <a-popconfirm
                    title="确定删除该所选文件？"
                    ok-text="确定"
                    cancel-text="取消"
                    @confirm="delCheckedFiles()">
                <template #icon><span class="anticon anticon-exclamation-circle"><i class="fa fa-circle-question danger-text"></i></span></template>
                <div class="ui button tiny text-small danger">删除</div>
            </a-popconfirm>
        </div>
    </div>
    <!-- 文件列表 网格式 -->
    <div class="fs-list" v-if="list.length > 0 && listType !== 'list'">
        <div :class="'item ' + file.type + ((file.checked || checkedAll) && ((selectable && file.type !== 'dir') || !selectable) ? ' checked' : '')" v-for="(file, index) in list">
            <div class="checker" v-if="(file.type === 'dir' && !selectable) || file.type !== 'dir'">
                <a-checkbox :checked="file.checked || checkedAll" @change="checkFile(index)"></a-checkbox>
            </div>
            <div class="preview" @click="clickFile(file, true, index)">
                <div class="warp">
                    <img :src="filterIcon(file)" :alt="file.name">
                </div>
            </div>
            <div class="name" @click="clickFile(file, false, index)">{{ file.name }}</div>
            <div class="options">
                <div class="option text-center" v-if="file.extension == 'zip'">
                    <a-popconfirm
                            title="确定解压该文件到当前文件夹？"
                            ok-text="确定"
                            cancel-text="取消"
                            @confirm="unzip(file.id, file.name)">
                        <template #icon><span class="anticon anticon-exclamation-circle"><i class="fa fa-circle-question danger-text"></i></span></template>
                        <div class="ui button tiny text-small success" style="padding: 0 5px;">解</div>
                    </a-popconfirm>
                </div>
                <div class="option text-center">
                    <div class="ui button tiny text-small" @click="editFile(file.id, file.name)" style="padding: 0 5px;">改</div>
                </div>
                <div class="option text-center">
                    <a-popconfirm
                            title="确定删除该文件？"
                            ok-text="确定"
                            cancel-text="取消"
                            @confirm="delFile(file.id)">
                        <template #icon><span class="anticon anticon-exclamation-circle"><i class="fa fa-circle-question danger-text"></i></span></template>
                        <div class="ui button tiny text-small danger" style="padding: 0 5px;">删</div>
                    </a-popconfirm>
                </div>
            </div>
        </div>
    </div>
    <!-- 文件列表 列表式 -->
    <div class="fs-list list" v-if="list.length > 0 && listType === 'list'">
        <div :class="'item ' + file.type + ((file.checked || checkedAll) && ((selectable && file.type !== 'dir') || !selectable) ? ' checked' : '')" v-for="(file, index) in list">
            <div class="checker" v-if="(file.type === 'dir' && !selectable) || file.type !== 'dir'">
                <a-checkbox :checked="file.checked || checkedAll" @change="checkFile(index)"></a-checkbox>
            </div>
            <div class="preview" @click="clickFile(file, true, index)">
                <div class="warp">
                    <img :src="filterIcon(file)" :alt="file.name">
                </div>
            </div>
            <div class="name" @click="clickFile(file, false, index)">{{ file.name }}</div>
            <div class="options">
                <div class="option" v-if="file.extension == 'zip'">
                    <a-popconfirm
                            placement="left"
                            title="确定要解压该文件到当前文件夹吗?"
                            ok-text="确定"
                            cancel-text="取消"
                            @confirm="unzip(file.id, file.name)">
                        <template #icon><i class="fa fa-circle-question"></i></template>
                        <div class="ui button success tiny text-small">解压</div>
                    </a-popconfirm>
                </div>
                <div class="option">
                    <div class="ui button tiny text-small" @click="editFile(file.id, file.name)">改名</div>
                </div>
                <div class="option text-right">
                    <a-popconfirm
                            placement="left"
                            title="确定删除该文件?"
                            ok-text="确定"
                            cancel-text="取消"
                            @confirm="delFile(file.id)">
                        <template #icon><i class="fa fa-circle-question"></i></template>
                        <div class="ui button tiny text-small danger">删除</div>
                    </a-popconfirm>
                </div>
            </div>
        </div>
    </div>
    <!-- 空列表 -->
    <div class="fs-empty line45deg-background" v-if="list.length === 0">
        <a-empty :image="simpleImage" description="没有文件记录">
            <div class="padding-top"></div>
            <a-button type="primary" danger @click="editFile(0)">
                <template #icon><i class="fa fa-folder-plus margin-right-half"></i></template>
                添加文件夹
            </a-button>
            <a-button type="primary" class="margin-left" @click="clickUp">
                <template #icon><i class="fa fa-up margin-right-half"></i></template>
                上传文件
            </a-button>
        </a-empty>
    </div>
    <!-- 正在上传的列表 -->
    <div class="fs-queue" :class="{ open:prepareList.length > 0 }">
        <!--  -->
        <div class="fs-queue-header">
            <div class="center justify-center text-bold">上传列表</div>
            <div class="end justify-center" v-if="!startUpload">
                <a-button type="danger" shape="circle" size="small" @click="clean()">
                    <template #icon><i class="fa fa-close"></i></template>
                </a-button>
            </div>
            <div class="end medium-text justify-center" v-else>文件正在上传，请等待!</div>
        </div>
        <!-- 列表 -->
        <div class="fs-queue-list">
            <div class="item" v-for="pf in prepareList">
                <div class="preview"><img :src="pf.preview" :alt="pf.name"></div>
                <div class="detail">
                    <!-- 文件名称 -->
                    <div class="name">{{ pf.name }}</div>
                    <!-- 等待上传 -->
                    <div class="state" v-if="pf.state === 2 && !pf.error">等待中...</div>
                    <!-- 上传进度条 -->
                    <div class="state" v-if="pf.state === 3 && !pf.error">
                        <a-progress :percent="pf.percent" size="small" status="active"></a-progress>
                    </div>
                    <!-- 上传错误 -->
                    <div class="danger-text state" v-if="pf.state === 0 || pf.error"><i class="fa fa-warning margin-right-half"></i> {{ pf.errorMessage }}</div>
                    <!-- 上传成功 -->
                    <div class="success-text state" v-if="pf.state === 1 && !pf.error"><i class="fa fa-check margin-right-half"></i> {{ pf.exists ? '已跳过(存在相同文件)' : '文件上传成功' }}</div>
                </div>
            </div>
        </div>
    </div>
</div>
<script>
    const { createApp, defineComponent, ref } = Vue;
    const { message, Empty, Modal } = antd;
    const app = createApp({
        //数据
        data() {
            return {
                //
                simpleImage: Empty.PRESENTED_IMAGE_SIMPLE,
                //文件夹图标
                dirIcon: "{static url='/framework/icons/folder-open.png'}",
                fileIcon: "{static url='/framework/icons/file.png'}",
                //列表类型
                listType: "grid",
                //是否可选
                selectable: parseInt("{$get.selectable|default:'0'}") === 1,
                multi: parseInt("{$get.multi|default:'0'}") === 1,
                //列表参数
                listQueryParams: {
                    parentId: 0,
                    groupName: "{$get.groupName|default:'other'}",
                    type: "{$get.type|default:'all'}",
                    keyword: ""
                },
                //临时关键字
                keyword: "",
                //文件列表
                list: [],
                //所有父级
                parents: [],
                //正在上传的文件列表
                prepareList: [],
                //开始上传
                startUpload: false,
                //接受什么文件
                accept: "*/*",
                //是否已上传完成
                uploadFinish: true,
                //是否允许多个文件同时上传
                multiple: parseInt("{$get.multiple|default:'1'}") == 1,
                //分片大小，可以降低数值进行进度尝试
                sliceSize: 0.5 * 1024 * 1024,
                //文件最大限制，不限制，请填写 0
                maxSize: 1024 * 1024 * 1024,
                //上传时使用的裁剪、重设大小参数
                resize: "{$get.resize|default:''}",
                //上传、处理结束的队列
                finishList: 0,
                //是否存在上传错误的文件
                errorFiles: 0,
                //预览图
                preview: null,
                //是否显示预览图
                previewVisible: false,
                //全选
                checkedAll: false,
                //已选数量
                checkedRows: 0,
                //编辑
                edit: {
                    id: 0,
                    name: '',
                    show: false,
                    loading: false
                }
            }
        },
        //
        mounted() {
            this.initData();
        },
        //方法
        methods: {
            //加载数据
            initData: function() {
                let self = this;
                if (this.listQueryParams.parentId > 0) {
                    axios.get('{url url="/fs/api/files/parents"}', {
                        params: {
                            id: this.listQueryParams.parentId
                        }
                    }).then(response => {
                        const res = response.data;
                        if (res.error === 0) {
                            self.parents = res.data;
                        } else {
                            message.error(res.message);
                        }
                    }).catch( error => {
                        message.error(error);
                    });
                } else {
                    this.parents = [];
                }
                this.getList();
            },
            //获取文件列表
            getList: function () {
                const hide = message.loading('正在加载数据...', 0);
                let self = this;
                axios.get('{url url="/fs/api/files/list"}', {
                    params: this.listQueryParams
                }).then(response => {
                    hide();
                    const res = response.data;
                    if (res.error === 0) {
                        self.list = res.data;
                        self.cancelCheckedAll();
                    } else {
                        message.error(res.message);
                    }
                }).catch( error => {
                    hide();
                    message.error(error);
                });
            },
            //设置列表参数
            setListQueryParams: function (params) {
                //通过替换key值的方式
                for(let key in params){
                    this.listQueryParams[key] = params[key];
                }
                this.getList();
            },
            //搜索
            setKeyword: function (value){
                if(this.listQueryParams.keyword != value){
                    this.listQueryParams.keyword = value;
                    this.getList();
                }
                if (value == '') {
                    this.keyword = '';
                }
            },
            //清除搜索
            cleanKeyword: function (){
                this.keyword = '';
                this.listQueryParams.keyword = '';
                this.getList();
            },
            //回到某个目录下
            setParent: function (id){
                this.listQueryParams.parentId = id;
                this.initData();
            },
            //点击上传文件
            clickUp: function (){
                if (this.startUpload) {
                    message.error("正在上传，请稍后");
                    return;
                }
                this.accept = this.listQueryParams.type === "image" ? "image/*" : (this.listQueryParams.type === "video" ? "video/mp4,video/avi" : "*/*");
                this.$refs.fileInput.dispatchEvent(new MouseEvent("click"));
            },
            //内容有所变动时，自动触发
            startUp: function (){
                if (this.startUpload) {
                    message.error("正在上传，请稍后");
                    return;
                }
                //上传前，先清除一篇旧队列
                this.prepareList = [];
                //读取文件
                for(let index = 0; index < this.$refs.fileInput.files.length; index++){
                    let file = this.$refs.fileInput.files[index];
                    //构建对象
                    let item = {
                        type: file.type,
                        preview: this.filterIcon(file),
                        sizeTransfer: this.bytesToSize(file.size),
                        name: file.name,
                        error: file.size > this.maxSize && this.maxSize > 0,
                        errorMessage: file.size > this.maxSize && this.maxSize > 0 ? '超出大小限制' : null,
                        state: 2,
                        resource: file,
                        //文件内容
                        data: "",
                        //总字节
                        size: file.size,
                        //已上传字节
                        sizeUploaded: 0,
                        //分片数量
                        sliceLength: this.sliceSize > file.size ? 1 : Math.ceil(file.size / this.sliceSize),
                        //已上传片
                        sliceUploaded: 0,
                        //上传进度比例
                        percent: 0,
                        //云端是否存在
                        exists: false
                    };
                    //加入队列
                    this.prepare(item);
                }
            },
            prepare: function (file) {
                this.prepareList.push(file);
                const index = this.prepareList.length - 1;
                if (!file.error) {
                    //H5文件读取对象
                    this.prepareList[index].reader = new FileReader();
                    let self = this;
                    this.prepareList[index].reader.onerror = function () {
                        //文件读取错误
                        self.prepareList[index].error = true;
                        self.prepareList[index].errorMessage = "文件读取失败";
                        self.prepareList[index].state = 0;
                    };
                    if (this.resize === "") {
                        //没有裁剪参数时，将使用MD5校验
                        this.prepareList[index].reader.onload = function (progressEvent) {
                            //首次处理
                            let spark = new SparkMD5.ArrayBuffer();
                            spark.append(progressEvent.target.result);
                            let hash = spark.end();
                            //检测MD5是否存在
                            axios.post("{url url='/fs/api/files/exists'}", {
                                hash,
                                parentId: self.listQueryParams.parentId,
                                groupName: self.listQueryParams.groupName,
                                name: self.prepareList[index].name
                            }).then( response => {
                                const res = response.data;
                                if (res.error !== 0) {
                                    //交给服务端处理
                                    self.read(index);
                                } else {
                                    //取到了数据，直接使用即可
                                    let data = res.data;
                                    self.prepareList[index].state = 1;
                                    self.prepareList[index].preview = self.filterIcon(data);
                                    self.prepareList[index].exists = true;
                                    self.finish(index);
                                    //添加到列表
                                    self.list.push({
                                        id:         data.id,
                                        exists:     true,
                                        extension:  data.extension,
                                        name:       data.name,
                                        src:        data.src,
                                        url:        data.url,
                                        type:       data.type,
                                        checked:    false,
                                        new:        true
                                    });
                                    //取消选择所有
                                    self.cancelCheckedAll();
                                }
                            }).catch( error => {
                                //发生了错误，提交给服务端处理
                                self.read(index);
                            });
                        };
                        //以二进制读取，然后判断MD5
                        this.prepareList[index].reader.readAsArrayBuffer(file.resource);
                    } else {
                        //如果存在 resize 参数，则直接提交文件给服务端处理
                        this.read(index);
                    }
                } else {
                    this.finish(index);
                }
            },
            //读取文件，触发上传
            read: function (index) {
                let file = this.prepareList[index];
                if (file.state === 2) {
                    //从未上传过
                    let self = this;
                    this.prepareList[index].reader.onload = function (progressEvent) {
                        //首次处理
                        if (self.prepareList[index].state === 2) {
                            //图片类型，首次读取时
                            switch (self.prepareList[index].type.toLowerCase()) {
                                case "image/jpeg" :
                                case "image/png" :
                                case "image/gif" :
                                case "image/jpg" :
                                case "image/bmp" :
                                case "image/webp" :
                                    //更换为图片数据展示
                                    self.prepareList[index].preview = progressEvent.target.result;
                                    break;
                                default:

                                    break;
                            }
                        }
                        self.prepareList[index].state = 3;
                        //上传读取到的数据
                        self.upload(index, progressEvent.target.result);
                    };
                }
                let reader = this.prepareList[index].reader;
                if (file.sliceLength > 1) {
                    if (file.sliceUploaded >= file.sliceLength) {
                        //上传完成了
                        return;
                    }
                    //需要分片上传
                    const start = file.sliceUploaded * this.sliceSize;
                    const end = (start + this.sliceSize) > file.size ? file.size : (start + this.sliceSize);
                    const blobSlice = File.prototype.mozSlice || File.prototype.webkitSlice || File.prototype.slice;
                    reader.readAsDataURL(blobSlice.call(file.resource, start, end));
                } else {
                    //不需要分片
                    reader.readAsDataURL(file.resource);
                }
            },
            //上传数据
            upload: function (index, base64) {
                let file = this.prepareList[index] || null;
                if(file && !file.error && file.state === 3) {
                    let self = this;
                    //AJAX POST
                    axios.post('{url url="/fs/api/upload"}', {
                        parentId: this.listQueryParams.parentId,
                        groupName: this.listQueryParams.groupName,
                        resize: this.resize,
                        base64,
                        fileName: file.name,
                        slice: file.sliceLength > 1 ? 1 : 0,
                        part: file.sliceUploaded + 1,
                        parts: file.sliceLength,
                        tmpName: file.tmpName || ""
                    }, {
                        onUploadProgress: function (progressEvent) {
                            self.progress(index, progressEvent.total, progressEvent.loaded);
                        },
                    }).then(response => {
                        const res = response.data;
                        if (res.error === 0) {
                            //上传成功
                            if (res.data.error === 0) {
                                self.success(index, res.data);
                            } else {
                                //文件上传失败
                                self.error(index, res.data.message);
                            }
                        } else {
                            //文件上传失败
                            self.error(index, res.message);
                        }
                    }).catch( error => {
                        //文件上传失败
                        self.error(index, '文件上传失败');
                    });
                }
            },
            //上传中...
            progress: function (index, total, loaded) {
                if(this.prepareList[index].state === 3) {
                    //此阶段的比例
                    const percent = loaded / total;
                    //总阶段数量
                    const parts = this.prepareList[index].sliceLength;
                    const part = this.prepareList[index].sliceUploaded;
                    //实际比例
                    let num = ((part + percent ) / parts * 100);
                    num = num >= 100 ? 100 : num;
                    this.prepareList[index].percent = Math.ceil(num);
                }
            },
            //单个文件上传成功
            success: function (index, data) {
                let file = this.prepareList[index] || null;
                if(file && !file.error && file.state === 3) {
                    //根据是否使用了分片上传进行处理
                    if (file.sliceLength > 1) {
                        //标记上传部分完成
                        this.prepareList[index].sliceUploaded++;
                        if (this.prepareList[index].sliceUploaded < this.prepareList[index].sliceLength) {
                            //临时文件名称
                            this.prepareList[index].tmpName = data.tmpName;
                            //分片未上传完成，继续读取下一段
                            return this.read(index);
                        }
                    }
                    //上传完成了
                    this.prepareList[index].state = 1;
                    this.prepareList[index].exists = data.exists === 1;
                    this.prepareList[index].preview = this.filterIcon(data);
                    this.list.push({
                        id:         data.id,
                        exists:     data.exists === 1,
                        extension:  data.extension,
                        name:       data.name,
                        src:        data.src,
                        url:        data.url,
                        type:       data.type,
                        checked:    false,
                        new:        true
                    });
                    //结束
                    this.finish(index);
                    //取消选择所有
                    this.cancelCheckedAll();
                }
            },
            //单个文件上传失败
            error: function (index, message) {
                this.prepareList[index].error = true;
                this.prepareList[index].errorMessage = message;
                this.prepareList[index].state = 0;
                //结束
                this.finish(index);
                //路出错误
                message.error(message ?? '文件上传失败');
            },
            //完成标记
            finish: function (index) {
                this.finishList++;
                if (this.finishList >= this.prepareList.length) {
                    this.startUpload = false;
                }
            },
            //文件大小转数值单位
            bytesToSize : function(bytes) {
                let sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
                if (bytes === 0) return '0KB';
                let i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
                return (bytes / Math.pow(1024, i)).toFixed(2) + ' ' + sizes[i];
            },
            clean: function () {
                this.prepareList = [];
            },
            //过滤器
            filterIcon: function (item) {
                if(item.type === 'image' && item.url) return item.url;
                let icon = "{static url='/framework/icons/'}";
                if(item.type.indexOf("image") === 0) return icon + "image.png";
                if(item.type === 'dir') return icon + "folder-open.png";
                let suffix = item.name.slice(item.name.lastIndexOf(".") + 1);
                let exists = "psd,pdf,ppt,rar,7z,zip,dmg,js,php,sql,txt,mp4,avi,mov,flv,wmv,mkv,json,log,pem".split(",");
                if(exists.indexOf(suffix) !== -1)
                {
                    return icon + suffix + ".png";
                }
                switch (suffix){
                    case "pptx":
                        return icon + "ppt.png";
                    case "docx":
                        return icon + "word.png";
                    case "xls":
                    case "xlsx":
                    case "cvs":
                        return icon + "excel.png";
                    case "html":
                    case "cs":
                    case "ts":
                    case "scss":
                    case "css":
                    case "sass":
                    case "less":
                    case "conf":
                        return icon + "code.png";
                    default:
                        return icon + "file.png";
                }
            },
            //删除文件
            delFile: function (id) {
                let self = this;
                axios.post("{url url='/fs/api/files/delete'}", { id }).then( ({ data }) => {
                    if (data.error !== 0) {
                        message.error(data.message);
                    } else {
                        message.success(data.message);
                        //重载数据
                        self.getList();
                    }
                }).catch(error => {
                    console.log(error);
                    message.error('删除失败');
                });
            },
            //删除选中的文件
            delCheckedFiles: function () {
                if (this.checkedRows > 0 || this.checkedAll) {
                    const hide = message.loading("正在删除选择的文件", 0);
                    let idList = [];
                    let self = this;
                    this.list.forEach(item => {
                        if ((item.checked || this.checkedAll) && ((item.type !== 'dir' && self.selectable) || !self.selectable)) {
                            idList.push(item.id);
                        }
                    });
                    if (idList.length > 0) {
                        axios.post("{url url='/fs/api/files/delete'}", {
                            id: idList.join(",")
                        }).then(({ data }) => {
                            if (data.error !== 0) {
                                message.error(data.message);
                            } else {
                                hide();
                                message.success(data.message);
                                //重载
                                self.getList();
                            }
                        }).catch(() => {
                            hide();
                            message.error("删除失败");
                        });
                    } else {
                        hide();
                        message.warning("没有选择文件");
                    }
                }
            },
            //编辑
            editFile: function (id, name, index) {
                this.edit.id = id;
                this.edit.index = index || 0;
                this.edit.name = name || '';
                this.edit.show = true;
            },
            //保存文件
            save: function () {
                if (this.edit.name.toString().length === 0) {
                    return message.error("请填写文件(夹)名称");
                }
                this.edit.loading = true;
                let self = this;
                axios.post("{url url='/fs/api/files/save'}", {
                    id: this.edit.id,
                    name: this.edit.name,
                    parentId: this.listQueryParams.parentId,
                    groupName: this.listQueryParams.groupName
                }).then(({ data }) => {
                    self.edit.loading = false;
                    if (data.error !== 0) {
                        message.error(data.message);
                    } else {
                        message.success(data.message);
                        if (self.edit.id > 0 && self.edit.index >= 0) {
                            self.list[self.edit.index].name = self.edit.name.toString();
                        } else {
                            self.getList();
                        }
                        self.edit.show = false;
                        self.edit.id = 0;
                        self.edit.name = '';
                    }
                }).catch(() => {
                    self.edit.loading = false;
                    message.error("保存失败，请稍后再试");
                });
            },
            //点击(如果页面存在选择，则可以选择)
            clickFile: function (file, preview, index) {
                if (file.type === 'image' && preview && !this.selectable) {
                    //预览图片
                    this.preview = file.url;
                    this.setPreviewVisible(true);
                    return;
                }
                if (file.type === 'dir') {
                    this.setParent(file.id);
                    return;
                }
                this.list[index].checked = this.checkedAll ? false : !this.list[index].checked;
                //处理
                let checkAll = true;
                this.checkedRows = 0;
                this.list.forEach((item, i) => {
                    item.checked = item.checked === undefined ? this.checkedAll : item.checked;
                    if (this.selectable && !this.multi && i != index) {
                        item.checked = false;
                    }
                    if (item.checked) {
                        this.checkedRows++;
                    } else {
                        checkAll = false;
                    }
                });
                this.checkedAll = checkAll;
            },
            //
            setPreviewVisible: function (value) {
                this.previewVisible = value;
            },
            //
            checkFile: function (index) {
                this.list[index].checked = this.checkedAll ? false : !this.list[index].checked;
                //处理
                let checkAll = true;
                this.checkedRows = 0;
                this.list.forEach((item, i) => {
                    item.checked = item.checked === undefined ? this.checkedAll : item.checked;
                    if (this.selectable && !this.multi && i != index) {
                        item.checked = false;
                    }
                    if (item.checked) {
                        this.checkedRows++;
                    } else {
                        checkAll = false;
                    }
                });
                this.checkedAll = checkAll;
            },
            //全选变更
            checkAllChange: function (_event) {
                let rows = 0;
                this.list.forEach(item => {
                    item.checked = _event.target.checked;
                    rows += _event.target.checked && ((this.selectable && item.type !== 'dir') || !this.selectable) ? 1 : 0;
                });
                this.checkedRows = rows;
            },
            //取消所有选择
            cancelCheckedAll: function () {
                this.checkedAll = false;
                this.list.forEach(item => {
                    item.checked = false;
                });
                this.checkedRows = 0;
            },
            unzip(id, name) {
                axios.post("{url url='/fs/api/files/unzip'}", {
                    id
                }).then(({ data }) => {
                    if (data.error !== 0) {
                        message.error(data.message);
                    } else {
                        unzipLoading[id] = message.loading("[正在解压]: " + name, 0);
                        setTimeout(() => {
                            checkUnzipState(id, name);
                        }, 1000);
                    }
                }).catch(() => {
                    message.error("服务端繁忙，请稍后再试");
                });
            }
        }
    }).use(antd);
    //外部可使用vm对象操作
    const vm = app.mount('#app-outlet');
    //存到 window，方便其它页面调用选择文件
    window.FilesManager = vm;
    //解压队列
    let unzipLoading = [];
    function checkUnzipState(id, name) {
        axios.get("{url url='/fs/api/files/unzipState'}", {
            params: {
                id
            }
        }).then(({ data }) => {
            if (data.error !== 0) {
                message.error(data.message);
            } else {
                if (data.data.state == 2) {
                    setTimeout(() => {
                        checkUnzipState(id, name);
                    }, 1000);
                } else {
                    antd.notification.success({
                        message: "文件解压完成",
                        description: "文件: " + name + "，已解压完成！"
                    });
                    if (unzipLoading[id]) {
                        unzipLoading[id]();
                    }
                    //重载
                    vm.getList();
                }
            }
        }).catch(() => {
            message.error("服务端繁忙，请稍后再试");
        });
    }
</script>
</body>
</html>